/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef CVML_COMPACT_INSTRUCTIONS_HPP
#define CVML_COMPACT_INSTRUCTIONS_HPP

void CompactInstructions(Instruction* first)
{
    Instruction* i = first;

    while (i != NULL) 
    {
        OpCodeEnum nextop = op_noop;
        if (i->next != NULL)
            nextop = i->next->opcode;

        switch (i->opcode) 
        {
        case op_getN:
            switch (i->operand) 
            {
            case 0: i->SetOpCode(op_dup); break;
            case 1: i->SetOpCode(op_get1); break;
            case 2: i->SetOpCode(op_get2); break;
            case 3: i->SetOpCode(op_get3); break;
            case 4: i->SetOpCode(op_get4); break;
            case 5: i->SetOpCode(op_get5); break;
            case 6: i->SetOpCode(op_get6); break;
            case 7: i->SetOpCode(op_get7); break;
            case 8: i->SetOpCode(op_get8); break;
            default: break;
            }
            // get(N) get(N+1) => get(N) dup
            if (nextop == op_getN && i->next->operand == i->operand + 1) {
                i->next->SetOpCode(op_dup);
            }
            break;
		case op_popN:
			switch(i->operand) 
			{
				case 1: i->SetOpCode(op_pop); break;
				case 2: i->SetOpCode(op_pop2); break;
				case 3: i->SetOpCode(op_pop3); break;
				case 4: i->SetOpCode(op_pop4); break;
				default : break;
			}
			break;
        case op_setN:
            switch (i->operand) 
            {
            case 0: i->SetOpCode(op_popd); break;
            case 1: i->SetOpCode(op_set1); break;
            case 2: i->SetOpCode(op_set2); break;
            case 3: i->SetOpCode(op_set3); break;
            case 4: i->SetOpCode(op_set4); break;
            case 5: i->SetOpCode(op_set5); break;
            case 6: i->SetOpCode(op_set6); break;
            case 7: i->SetOpCode(op_set7); break;
            case 8: i->SetOpCode(op_set8); break;
            default : break;
            }
            break;
        case op_push_sub:
			Assert(i->operand != -1);
            if (i->operand <= MAX_SUB_OPCODES) {
                i->SetOpCode((OpCodeEnum)(op_push_sub0 + i->operand));
            }
			else if (i->operand <= 255) {
				i->SetOpCode(op_push_sub_short, i->operand);
			}
	        break;
		case op_push_int:
            if (i->operand > -128 && i->operand < 128)
            {
                switch (i->operand)
                {
                case -1: i->SetOpCode(op_push_neg1); break;
                case 0: i->SetOpCode(op_push0); break;
                case 1: i->SetOpCode(op_push1); break;
                case 2: i->SetOpCode(op_push2); break;
				default: i->SetOpCode(op_push_byte, i->operand); 
                }                        
                break;

                switch (nextop)
                {
                case op_add: 
                    i->next->SetNoop();
                    // Check for arithmetic identities
                    switch (i->operand) 
                    {
                    case 1: i->SetOpCode(op_inc); break;
                    case -1: i->SetOpCode(op_dec); break;
                    case 0: i->SetNoop(); break;
                    }
                    break;
                case op_sub_int: 
                    i->next->SetNoop();
                    // Check for arithmetic identities
                    switch (i->operand) 
                    {
                    case 1: i->SetOpCode(op_dec); break;
                    case -1: i->SetOpCode(op_inc); break;
                    case 0: i->SetNoop(); break;
                    }
                    break;
                case op_mul_int: 
                    i->next->SetNoop();
                    // Check for arithmetic identities
                    switch (i->operand) 
                    {
                    case 1: i->SetNoop(); break;
                    case -1: i->SetOpCode(op_neg); break;
                    case 0: i->SetOpCode(op_push0); break;
                    }
                    break;
                case op_div_int: 
                    i->next->SetNoop();
                    // Check for arithmetic identities
                    switch (i->operand) 
                    {
                    case 1: i->SetNoop(); break;
                    case -1: i->SetOpCode(op_neg); break;
                    }
                    break;
                case op_mod_int:
                    i->next->SetNoop();
                    // Check for arithmetic identities
                    switch (i->operand) 
                    {
                    case 1: i->SetOpCode(op_push0); break;
                    case -1: i->SetOpCode(op_push0); break;
                    }
                    break;
                }
            }
		default:
            break;
        }
        if (i != NULL) 
            i = i->next;
    }
}

void CombineRepeaters(Instruction* i) {
	while (i != NULL) 
	{
		switch (i->opcode) 
		{
		case op_dup:
            {
                Instruction* tmp = i->next;
                int cnt = 1;
                while (tmp != NULL && (tmp->opcode == op_dup || tmp->opcode == op_noop)) {
                    tmp->SetNoop();
                    ++cnt;
					tmp = tmp->next;
                }
                switch (cnt) 
                {
                case 1: break;
                case 2: i->SetOpCode(op_dup2); break;
                case 3: i->SetOpCode(op_dup3); break;
                default: i->SetOpCode(op_dupN, cnt); break;                    
                }
            }
            break;
		case op_pop:
            {
                Instruction* tmp = i->next;
                int cnt = 1;
                while (tmp != NULL && (tmp->opcode == op_pop || tmp->opcode == op_noop)) {
                    tmp->SetNoop();
                    ++cnt;
					tmp = tmp->next;
                }
                switch (cnt) 
                {
                case 1: break;
                case 2: i->SetOpCode(op_pop2); break;
                case 3: i->SetOpCode(op_pop3); break;
				case 4: i->SetOpCode(op_pop4); break;
				case 5: i->SetOpCode(op_pop5); break;
				case 6: i->SetOpCode(op_pop6); break;
				case 7: i->SetOpCode(op_pop7); break;
				case 8: i->SetOpCode(op_pop8); break;
                default: i->SetOpCode(op_popN, cnt); break;                    
                }
            }
            break;
		}
		i = i->next;
	}
}

void CompactInstructions(ByteCode* bc)
{
    for (int i=0; i < bc->GetNumSubs(); ++i) {
        CompactInstructions(bc->GetSub(i)->first);     
    }
	for (int i=0; i < bc->GetNumSubs(); ++i) {
		CombineRepeaters(bc->GetSub(i)->first);
	}
    bc->StripNoops();
}

#endif
